package org.zhaya.sdk.util;

import cn.hutool.core.io.FastByteArrayOutputStream;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author luka
 * @description
 * @date 2022/9/11
 */
@Slf4j
public class PhoneNumberGeoUtils {
    private static final int INDEX_SEGMENT_LENGTH = 9;
    // https://gitee.com/whf0000/haomaguishudichaxun/tree/master/
    private static final String PHONE_DAT_FILE_PATH = "phone.dat";
    // https://ditu.so.com/zt/areacode.html
    private static final String FIXED_PHONE_JSON_FILE_PATH = "fixedPhoneGeo.json";

    private static byte[] dataByteArray;
    private static int indexAreaOffset;
    private static int phoneRecordCount;
    private static ByteBuffer byteBuffer;
    private static final Map<String, String> FIXED_PHONE_MAP = Maps.newConcurrentMap();

    /**
     * 用于匹配固定电话号码
     */
    private static final Pattern PATTERN_FIXED_PHONE = Pattern.compile("^(010|02\\d|0[3-9]\\d{2})?\\d{6,8}$");
    /**
     * 用于获取固定电话中的区号
     */
    private static final Pattern PATTERN_ZIPCODE = Pattern.compile("^(010|02\\d|0[3-9]\\d{2})\\d{6,8}$");

    static {
        try {
            if (Objects.isNull(dataByteArray)) {
                FastByteArrayOutputStream byteData = IoUtil.read(PhoneNumberGeoUtils.class.getClassLoader().getResourceAsStream(PHONE_DAT_FILE_PATH));
                dataByteArray = byteData.toByteArray();
                byteBuffer = ByteBuffer.wrap(dataByteArray);
                byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
                int dataVersion = byteBuffer.getInt();
                indexAreaOffset = byteBuffer.getInt();
                phoneRecordCount = (dataByteArray.length - indexAreaOffset) / INDEX_SEGMENT_LENGTH;
                log.info("PhoneNumberGeoUtils dataVersion: {}", dataVersion);
            }
            if (FIXED_PHONE_MAP.isEmpty()) {
                String context = IoUtil.readUtf8(PhoneNumberGeoUtils.class.getClassLoader().getResourceAsStream(FIXED_PHONE_JSON_FILE_PATH));
                JSONObject countryPhones = JSONUtil.parseObj(context);
                countryPhones.keySet().forEach(province -> {
                    JSONObject cities = countryPhones.getJSONObject(province);
                    for (Map.Entry<String, Object> city : cities) {
                        FIXED_PHONE_MAP.put(city.getKey(), province + city.getValue());
                    }
                });
            }
        } catch (Exception e) {
            log.error("PhoneToLocation Processor: phone load from store failed:", e);
        }
    }

    public static String lookup(String phoneNumber) {
        if (StringUtils.isNotBlank(phoneNumber)) {
            phoneNumber = phoneNumber.replaceAll(StrUtil.DASHED, StrUtil.EMPTY);
            if (isFixedPhone(phoneNumber)) {
                return fixedPhone(phoneNumber);
            } else {
                return mobilePhone(phoneNumber);
            }
        }
        return null;
    }

    /**
     * 解析手机号归属地
     *
     * @param phoneNumber 手机号
     * @return 归属地
     */
    private static String mobilePhone(String phoneNumber) {
        if (phoneNumber.length() > 11 || phoneNumber.length() < 7) {
            return null;
        }
        int phoneNumberPrefix;
        phoneNumberPrefix = Integer.parseInt(phoneNumber.substring(0, 7));
        int left = 0;
        int right = phoneRecordCount;
        while (left <= right) {
            int middle = (left + right) >> 1;
            int currentOffset = indexAreaOffset + middle * INDEX_SEGMENT_LENGTH;
            if (currentOffset >= dataByteArray.length) {
                return null;
            }
            byteBuffer.position(currentOffset);
            int currentPrefix = byteBuffer.getInt();
            if (currentPrefix > phoneNumberPrefix) {
                right = middle - 1;
            } else if (currentPrefix < phoneNumberPrefix) {
                left = middle + 1;
            } else {
                int infoBeginOffset = byteBuffer.getInt();
                int infoLength = -1;
                for (int i = infoBeginOffset; i < indexAreaOffset; ++i) {
                    if (dataByteArray[i] == 0) {
                        infoLength = i - infoBeginOffset;
                        break;
                    }
                }
                String infoString = new String(dataByteArray, infoBeginOffset, infoLength, StandardCharsets.UTF_8);
                String[] infoSegments = infoString.split("\\|");
                return infoSegments[0] + infoSegments[1];
            }
        }
        return null;
    }

    /**
     * 解析固定电话归属地
     *
     * @param phoneNumber 固定电话号
     * @return 归属地
     */
    private static String fixedPhone(String phoneNumber) {
        Matcher matcher = PATTERN_ZIPCODE.matcher(phoneNumber);
        if (matcher.find()) {
            return FIXED_PHONE_MAP.get(matcher.group(1));
        }
        return null;
    }

    /**
     * 判断是否为固定电话号码
     *
     * @param number 固定电话号码
     * @return true/false
     */
    private static boolean isFixedPhone(String number) {
        Matcher match = PATTERN_FIXED_PHONE.matcher(number);
        return match.matches();
    }
}
